package cn.dagteam.springboot.mongodb.starter.repository.support;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.ReflectionUtils;

import cn.dagteam.springboot.mongodb.starter.Constants;
import cn.dagteam.springboot.mongodb.starter.tools.Converts;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
public class QueryRequestBuilder {

	private List<QueryFilter> filters = new ArrayList<>();

	private Sort sort = Sort.unsorted();

	private Class<?> entityClass;
	
	public static QueryRequestBuilder builder() {
		QueryRequestBuilder builder = new QueryRequestBuilder();
		return builder;
	}

	public QueryRequestBuilder filter(QueryFilter filter) {
		this.filters.add(filter);
		return this;
	}

	public QueryRequestBuilder filters(Collection<QueryFilter> filters) {
		this.filters.addAll(filters);
		return this;
	}

	public QueryRequestBuilder sort(Sort sort) {
		this.sort = sort;
		return this;
	}

	public QueryRequestBuilder sort(Direction direction, String property) {
		sort.and(Sort.by(direction, property));
		return this;
	}

	public QueryRequestBuilder entityClass(Class<?> entityClass) {
		this.entityClass = entityClass;
		return this;
	}

	public Query build() {
		// 把查询条件创建了
		Query query = new Query();
		// 默认都是And的关系
		Map<String, CriteriaDefinition> criterias = new HashMap<>();
		// 由于会出现同一个字段多个条件的情况，需要用Map来判断
		filters.parallelStream().forEach(filter -> createCriteria(filter, criterias));
		criterias.values().parallelStream().forEach(p -> query.addCriteria(p));
		return query;
	}

	public void createCriteria(QueryFilter filter, Map<String, CriteriaDefinition> criterias) {
        String fieldName = filter.getFieldName();
        if (StringUtils.contains(fieldName, Constants.FILTER_OR_OPERATOR)) {
			String[] fieldNames = StringUtils.split(fieldName, Constants.FILTER_OR_OPERATOR);
			Criteria c = new Criteria();
			c.orOperator(Arrays.stream(fieldNames).map(f -> {return buildCriteria(filter, f, new Criteria(f));}).toArray(Criteria[]::new));
			criterias.put(fieldName, c);
		} else {
			Criteria c = (Criteria) criterias.get(fieldName);
	        if (c == null) {
	            c = new Criteria(fieldName);
	            criterias.put(fieldName, c);
	        }
	        buildCriteria(filter, fieldName, c);
		}
    }

	private Criteria buildCriteria(QueryFilter filter, String fieldName, Criteria c) {
		Operator op = filter.getOp();
        Object value = filter.getValue();
        log.debug("查询条件：{}", filter.toString());
        // 多级查询字段名称，确定字段类型
        if (StringUtils.isBlank(fieldName)) {
        	throw new RuntimeException("查询字段名不能为null");
		}
        Class<?> type = getFieldType(fieldName);
        log.debug("查询字段类型：{}", type.getSimpleName());
        // 转换为响应的数据类型
		Object byClassValue = changeTypeClass(value, type);
        Method method = null;
        // 特殊的处理
        if (StringUtils.isNotBlank(op.getPattern())) {
            switch (op) {
                case LIKE:
                case LLIKE:
                case RLIKE:
                    // 用正则的方式处理模糊查询
                	method = ReflectionUtils.findMethod(Criteria.class, op.getMethodName(), Pattern.class);
                    byClassValue = Pattern.compile(MessageFormat.format(op.getPattern(), byClassValue), Pattern.CASE_INSENSITIVE);
                    break;
                case EXISTS:
                case NEXISTS:
                	method = ReflectionUtils.findMethod(Criteria.class, op.getMethodName(), boolean.class);
                    byClassValue = Boolean.parseBoolean(op.getPattern());
                    break;
                default:
                    break;
            }
        } else {// 
        	switch (op) {
        	case IN:
            case NOTIN:
                // in和not in需要集合类型
            	if (byClassValue instanceof Collection) {
        			method = ReflectionUtils.findMethod(Criteria.class, op.getMethodName(), Collection.class);
        		} else if (byClassValue.getClass().isArray()) {
        			method = ReflectionUtils.findMethod(Criteria.class, op.getMethodName(), Object[].class);
        		}
                if (!(byClassValue instanceof Collection || byClassValue.getClass().isArray())) {
                    byClassValue = Converts.toArrayByClass(StringUtils.split(byClassValue.toString(), Constants.ARRAY_STRING_DELIMITER),
                            type);
                    method = ReflectionUtils.findMethod(Criteria.class, op.getMethodName(), Object[].class);
                }
                break;
			default:
				break;
			}
        }
        if (method == null) {
        	method = ReflectionUtils.findMethod(Criteria.class, op.getMethodName(), Object.class);
        	if (method == null) {
        		throw new RuntimeException("未知的判断条件：" + op.getMethodName());
			}
        }
        // 执行反射的方法
        ReflectionUtils.invokeMethod(method, c, byClassValue);
        return c;
	}

	private Object changeTypeClass(Object value, Class<?> type) {
		Object byClassValue = null;
    	if (value instanceof Collection) {
    		@SuppressWarnings({"unchecked" })
			Collection<Object> col = ((Collection<Object>) value);
    		List<Object> list = new ArrayList<>();
    		for (Object object : col) {
    			list.add(Converts.toByClass(object, type));
			}
    		byClassValue = list;
		} else if (value.getClass().isArray()) {
			byClassValue = Converts.toArrayByClass((Object[])value, type);
		} else {
			byClassValue = Converts.toByClass(value, type);
		}
		return byClassValue;
	}

	private Class<?> getFieldType(String fieldName) {
		Field field = null;
        Class<?> type = this.entityClass;
    	for (String f : fieldName.split("\\.")) {
    		field = ReflectionUtils.findField(type, f);
    		// 不存在的字段说明，传入的参数错误
			if (field == null) {
	            throw new RuntimeException("未知的查询字段：" + fieldName);
	        }
			type = field.getType();
		}
		return type;
	}

}
